package Interaction;
// Includes all utility libraries for handling Vectors, Enumerators, Randoms etc.
import java.util.*;

public class Environment 
{
	private static int size_ = 0, rounds_ = 0, coopCost_ = 0, defCost_ = 0, reward_ = 0;
	int deadAmount_ = 0;
	private Vector<Agent> v_ = new Vector<Agent>(0);
	static Random generator = new Random();
	
	Environment(int size, int division, int coopCost, int defCost, int reward)
	{
		this.size_ = size;
		this.rounds_ = size/3;
		this.coopCost_ = coopCost;
		this.defCost_ = defCost;
		this.reward_ = reward;
		initiate(division);
	}
	
	void interaction()
	{
		if(getV_().isEmpty())
			System.out.print("There are no elements in the Vector.\n");
		
		else{
			for(int i = 0 ; i < rounds_ ; i++)
			{
				Agent obj1 = getRandomAgent(getV_()), obj2 = getRandomAgent(getV_()), obj3 = getRandomAgent(getV_());
				
				boolean SC = ((obj1.getMove() && (obj2.getMove() || obj3.getMove())) || (obj2.getMove() && (obj1.getMove() || obj3.getMove())) || (obj3.getMove() && (obj1.getMove() || obj2.getMove())));
				
				if(SC)
				{
					if(obj1.getMove()){
						obj1.setPayoff(obj1.getPayoff() + reward_ - coopCost_);}
					else{
						obj1.setPayoff(obj1.getPayoff() + reward_ - defCost_);}
					if(obj2.getMove()){
						obj2.setPayoff(obj2.getPayoff() + reward_ - coopCost_);}
					else{
						obj2.setPayoff(obj2.getPayoff() + reward_ - defCost_);}
					if(obj3.getMove()){
						obj3.setPayoff(obj3.getPayoff() + reward_ - coopCost_);}
					else{
						obj3.setPayoff(obj3.getPayoff() + reward_ - defCost_);}
				}
				else
				{
					if(obj1.getMove()){
						obj1.setPayoff(obj1.getPayoff() - coopCost_);}
					else{
						obj1.setPayoff(obj1.getPayoff() - defCost_);}
					if(obj2.getMove()){
						obj2.setPayoff(obj2.getPayoff() - coopCost_);}
					else{
						obj2.setPayoff(obj2.getPayoff() - defCost_);}
					if(obj3.getMove()){
						obj3.setPayoff(obj3.getPayoff() - coopCost_);}
					else{
						obj3.setPayoff(obj3.getPayoff() - defCost_);}
				}
				obj1.setPlayedTotal(true);
				obj2.setPlayedTotal(true);
				obj3.setPlayedTotal(true);
				
				obj1.setPlayedThisRound(false);
				obj2.setPlayedThisRound(false);
				obj3.setPlayedThisRound(false);
			}
		}
	}
	
	void mortality(float deathRatio)
	{
		int tempRatio = (size_*Math.round(deathRatio))/100;
		for(int i = 0 ; i < tempRatio ; i++)
		{
			v_.removeElement(getRandomAgent(v_));
			deadAmount_++;
		}
	}

	void printAll()
	{
		long coopFitness = 0, defFitness = 0, coopNum = 0, defNum = 0;
		
		// Adds all rewards for cooperating- and defecting Agents respectively.
		for(int i = 0 ; i < (getV_().size()) ; i++)
		{
			if(getV_().elementAt(i).getMove()){
				coopFitness += getV_().elementAt(i).getPayoff();
				coopNum++;
			}
			else{
				defFitness += getV_().elementAt(i).getPayoff();
				defNum++;
			}
		}
		// Divides the additions to obtain average value.
		float avgCoopFitness = coopFitness/coopNum, avgDefFitness = defFitness/defNum;
		// Prints average value to console.
		System.out.println("\nAverage cooperative fitness: " + avgCoopFitness + ", calculated for " + coopNum + " cooperating Agents.");
		System.out.println("Average defect fitness: " + avgDefFitness + ", calculated for " + defNum + " defecting Agents.");
	}
	
	void reproduction()
	{
		// Gets average coop payoff taken into account the number of games played.
		double coopFitness = 0, defFitness = 0, coopNum = 0, defNum = 0;
		for(int i = 0 ; i < (getV_().size()) ; i++)
		{
			if(getV_().elementAt(i).getMove()){
				coopFitness += getV_().elementAt(i).getPayoff();
				coopNum += getV_().elementAt(i).getPlayedTotal();
			}
			else{
				defFitness += getV_().elementAt(i).getPayoff();
				defNum += getV_().elementAt(i).getPlayedTotal();
			}
		}
		double avgCoopFitness = coopFitness/coopNum, avgDefFitness = defFitness/defNum;
		
		// Calculates the chances of a cooperator reproducing relative to the chance of a defecter.
		double coopStuff = (avgCoopFitness*(coopNum/getV_().size()));
		double defStuff = (avgDefFitness*(defNum/getV_().size()));
		double coopFec = coopStuff/(coopStuff+defStuff);
		
		System.out.println("Coop fecundity: " + coopFec);
		
		for(int i = 0 ; i < deadAmount_ ; i++)
		{
			// Inserts Agent in relation to the above calculation.
			if(Math.random() <= coopFec)
				getV_().add(new Agent(0,true));
			else
				getV_().add(new Agent(0,false));
		}
		
		// Resets payoff for all elements in the vector.
		size_ = getV_().size();
		deadAmount_ = 0;
		for(int i = 0 ; i < (getV_().size()) ; i++){
			getV_().elementAt(i).setPayoff(0);
		}
	}
	
	private void initiate(int division)
	{
		int tempDiv = (size_*Math.round(division))/100;
		// Constructing Agent objects, with different attributes, and adding them to the Vector v.
		for(int i = 0 ; i < tempDiv ; i++){
			getV_().add(new Agent(i, true));
		}
		
		for(int i = tempDiv ; i < (size_) ; i++){
			getV_().add(new Agent(i, false));
		}
	}
	
	private static Agent getRandomAgent(Vector<Agent> vRand)
	{		
		int random = generator.nextInt(vRand.size());
		// Gets a random Agent from the vector.
		Agent randAgent = (Agent)vRand.get(random);
		
		// Ensures no self-interaction, and no more games than the specified "rounds_".
		while(randAgent.getPlayedThisRound())
		{
			random = generator.nextInt(vRand.size());
			randAgent = (Agent)vRand.get(random);
		}
			randAgent.setPlayedThisRound(true);
			return randAgent;
	}

	public Vector<Agent> getV_() {
		return v_;
	}

	public void setV_(Vector<Agent> v_) {
		this.v_ = v_;
	}	
}